package com.hydra.auth.config;

import com.hydra.auth.config.converter.UserAuthenticationConverter;
import com.hydra.auth.config.filter.OAuth2ClientCredentialsTokenEndpointFilter;
import com.hydra.auth.config.points.OAuth2AuthenticationEntryPoint;
import com.hydra.auth.config.points.OAuth2WebResponseExceptionTranslator;
import com.hydra.auth.extension.captcha.CaptchaTokenGranter;
import com.hydra.auth.service.LoginService;
import com.hydra.auth.service.WebUserDetailsService;
import com.hydra.auth.service.refresh.PreAuthenticatedUserDetailsService;
import com.hydra.base.common.Constant;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.security.KeyPair;
import java.util.*;

/**
 * 认证服务器配置
 * Created by macro on 2020/6/19.
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {

    private static final String kEY_STORE_PASSWORD = "Chenyumin";

    @Resource
    private AuthenticationManager authenticationManager;
    @Resource
    private JwtTokenEnhancer tokenEnhancer;
    @Resource
    private LoginService loginService;
    @Resource
    private DataSource dataSource;

    @Resource
    private WebUserDetailsService webUserDetailsService;

    /**
     * OAuth2客户端
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(jdbcClientDetailsService());
    }

    /**
     * 配置授权（authorization）以及令牌（token）的访问端点和令牌服务(token services)
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        // Token增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
        tokenEnhancers.add(tokenEnhancer);
        tokenEnhancers.add(accessTokenConverter());
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);

        //token存储模式设定 默认为InMemoryTokenStore模式存储到内存中
        endpoints.tokenStore(jwtTokenStore());

        endpoints.exceptionTranslator(new OAuth2WebResponseExceptionTranslator());

        // 获取原有默认授权模式(授权码模式、密码模式、客户端模式、简化模式)的授权者
        List<TokenGranter> granterList = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter()));

        // 添加验证码授权模式授权者
        granterList.add(new CaptchaTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
                endpoints.getOAuth2RequestFactory(), authenticationManager, loginService
        ));

//        // 添加手机短信验证码授权模式的授权者
//        granterList.add(new SmsCodeTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
//                endpoints.getOAuth2RequestFactory(), authenticationManager
//        ));
//
//        // 添加微信授权模式的授权者
//        granterList.add(new WechatTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
//                endpoints.getOAuth2RequestFactory(), authenticationManager
//        ));

        CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList);
        endpoints
                .authenticationManager(authenticationManager)
                .accessTokenConverter(accessTokenConverter())
                .tokenEnhancer(tokenEnhancerChain)
                .tokenGranter(compositeTokenGranter)
                .tokenServices(tokenServices(endpoints))
        ;
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        //自定义ClientCredentialsTokenEndpointFilter处理器，用于处理客户端id、密码错误的异常
        OAuth2ClientCredentialsTokenEndpointFilter filter = new OAuth2ClientCredentialsTokenEndpointFilter(security, new OAuth2AuthenticationEntryPoint());
        filter.afterPropertiesSet();

        security.tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
                //一定不要添加allowFormAuthenticationForClients，否则自定义的OAuth2ClientCredentialsTokenEndpointFilter不生效
//                .allowFormAuthenticationForClients()
                .addTokenEndpointAuthenticationFilter(filter)
        ;
    }


    /**
     * jwt token存储模式
     */
    @Bean
    public JwtTokenStore jwtTokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }


    public DefaultTokenServices tokenServices(AuthorizationServerEndpointsConfigurer endpoints) {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
        tokenEnhancers.add(tokenEnhancer);
        tokenEnhancers.add(accessTokenConverter());
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);

        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(jdbcClientDetailsService());
        tokenServices.setTokenEnhancer(tokenEnhancerChain);
        // token默认过期时间(这里配置没用，具体是取数据库的oauth_client_details配置表)
        tokenServices.setAccessTokenValiditySeconds(7200);
        // refresh_token默认过期时间
        tokenServices.setRefreshTokenValiditySeconds(86400);

        // 多用户体系下，刷新token再次认证客户端ID和 UserDetailService 的映射Map
        Map<String, UserDetailsService> clientUserDetailsServiceMap = new HashMap<>();
        clientUserDetailsServiceMap.put(Constant.USER_PLATFORM.WEB, webUserDetailsService); // 系统管理客户端
//        clientUserDetailsServiceMap.put(SecurityConstants.APP_CLIENT_ID, userDetailsService); // Android、IOS、H5 移动客户端
//        clientUserDetailsServiceMap.put(SecurityConstants.WEAPP_CLIENT_ID, userDetailsService); // 微信小程序客户端

        // 刷新token模式下，重写预认证提供者替换其AuthenticationManager，可自定义根据客户端ID和认证方式区分用户体系获取认证用户信息
        PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
        provider.setPreAuthenticatedUserDetailsService(new PreAuthenticatedUserDetailsService<>(clientUserDetailsServiceMap));
        tokenServices.setAuthenticationManager(new ProviderManager(Collections.singletonList(provider)));

        /**
         * refresh_token有两种使用方式：重复使用(true)、非重复使用(false)，默认为true
         *  1 重复使用：access_token过期刷新时， refresh_token过期时间未改变，仍以初次生成的时间为准
         *  2 非重复使用：access_token过期刷新时， refresh_token过期时间延续，在refresh_token有效期内刷新便永不失效达到无需再次登录的目的
         */
        tokenServices.setReuseRefreshToken(true);
        return tokenServices;

    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        accessTokenConverter.setKeyPair(keyPair());

        DefaultAccessTokenConverter defaultAccessTokenConverter = new DefaultAccessTokenConverter();
        defaultAccessTokenConverter.setUserTokenConverter(new UserAuthenticationConverter());
        // 赋予新的Token转换器。
        accessTokenConverter.setAccessTokenConverter(defaultAccessTokenConverter);

        return accessTokenConverter;
    }

    @Bean
    public KeyPair keyPair() {
        //从classpath下的证书中获取秘钥对
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), kEY_STORE_PASSWORD.toCharArray());
        return keyStoreKeyFactory.getKeyPair("jwt", kEY_STORE_PASSWORD.toCharArray());
    }


    /**
     * 获取数据源
     */
    @Bean
    public JdbcClientDetailsService jdbcClientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

}